import("cpp");

CMatrix = new cpp.Plugin("CMatrix");
with (CMatrix) {
    Include("<string.h>");
    Include("../plugin/CMatrix.h");

    Extra = <%
        Persistent<Function> IntegerRow;
        Persistent<Function> DoubleRow;
        extern Persistent<Object> ClassDict;

        cmatrix_double *GetDoubleCMatrix( Handle<Value> arg )
        {
            if ( !arg->IsObject() )
                return NULL;

            return (cmatrix_double *)Local<External>::Cast( arg->ToObject()->GetInternalField( 1 ) )->Value();
        }
    %>

    Init = <%
        IntegerRow = Persistent<Function>::New( Local<Function>::Cast( obj->Get( String::New( "__IntegerRow__" ) ) ) );
        DoubleRow = Persistent<Function>::New( Local<Function>::Cast( obj->Get( String::New( "__DoubleRow__" ) ) ) );

        // Expose for other plugins to use
        Handle<Object> Handle_CMatrix_Int = obj->Get( String::New( "Integer" ) )->ToObject();
        Handle<Object> Handle_CMatrix_Dbl = obj->Get( String::New( "Double" ) )->ToObject();
        ClassDict->Set( String::New( "CMatrix_Integer" ), Handle_CMatrix_Int );
        ClassDict->Set( String::New( "CMatrix_Double" ), Handle_CMatrix_Dbl );
       
    %>

    Exit = <%
        IntegerRow.Dispose();
        IntegerRow.Clear();
        DoubleRow.Dispose();
        DoubleRow.Clear();
    %>

    // ROW OF DOUBLE MATRIX
    Classes.__DoubleRow__ = new Class("__DoubleRow__", 1);
    with (Classes.__DoubleRow__) {
        Constructor = <%
            args.This()->SetInternalField( 0, args[0] );
        %>

        Destructor = <%
            cmatrix_double_row *row = (cmatrix_double_row *)Local<External>::Cast( obj->ToObject()->GetInternalField( 0 ) )->Value();
            delete row;
        %>

        IndexedGetter = <%  
            // Check bounds
            cmatrix_double_row *row = (cmatrix_double_row *)Local<External>::Cast( info.This()->GetInternalField( 0 ) )->Value();
            if ( ( index < 0 ) || ( index >= row->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );

            // Get array field
            return Number::New( row->data[index] );
        %>

        IndexedSetter = <%
            // Check bounds
            cmatrix_double_row *row = (cmatrix_double_row *)Local<External>::Cast( info.This()->GetInternalField( 0 ) )->Value();
            if ( ( index < 0 ) || ( index >= row->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );
            
            // Set array field
            row->data[index] = value->NumberValue();
        %>

        Getters.length = <%
            cmatrix_double_row *row = (cmatrix_double_row *)Local<External>::Cast( info.This()->GetInternalField( 0 ) )->Value();
            return Int32::New( row->size );
        %>
    }

    // ROW OF INTEGER MATRIX
    Classes.__IntegerRow__ = new Class("__IntegerRow__", 1);
    with (Classes.__IntegerRow__) {
        Constructor = <%
            args.This()->SetInternalField( 0, args[0] );
        %>

        Destructor = <%
            cmatrix_int_row *row = (cmatrix_int_row *)Local<External>::Cast( obj->ToObject()->GetInternalField( 0 ) )->Value();
            delete row;
        %>

        IndexedGetter = <%  
            // Check bounds
            cmatrix_int_row *row = (cmatrix_int_row *)Local<External>::Cast( info.This()->GetInternalField( 0 ) )->Value();
            if ( ( index < 0 ) || ( index >= row->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );

            // Get array field
            return Int32::New( row->data[index] );
        %>

        IndexedSetter = <%
            // Check bounds
            cmatrix_int_row *row = (cmatrix_int_row *)Local<External>::Cast( info.This()->GetInternalField( 0 ) )->Value();
            if ( ( index < 0 ) || ( index >= row->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );
            
            // Set array field
            row->data[index] = value->Int32Value();
        %>

        Getters.length = <%
            cmatrix_int_row *row = (cmatrix_int_row *)Local<External>::Cast( info.This()->GetInternalField( 0 ) )->Value();
            return Int32::New( row->size );
        %>
    }

    // DOUBLE MATRIX
    Classes.Double = new Class ("Double", 2);
    with (Classes.Double) {
        Constructor = <%
            // Check arguments
            if ( args.Length() < 2 )
                return ThrowException( String::New( "Expected row, column count" ) );
            else if ( !args[0]->IsInt32() )
                return ThrowException( String::New( "Row count must be integer" ) );
            else if ( !args[1]->IsInt32() )
                return ThrowException( String::New( "Column count must be integer" ) );
            else if ( args[0]->IntegerValue() < 1 )
                return ThrowException( String::New( "Invalid row count" ) );
            else if ( args[1]->IntegerValue() < 1 )
                return ThrowException( String::New( "Invalid column count" ) );

            // Allocate space
            cmatrix_double *matrix = new cmatrix_double;
            matrix->rows = args[0]->Int32Value();
            matrix->cols = args[1]->Int32Value();
            int size = matrix->rows * matrix->cols;
            matrix->data = new double[size];
            args.This()->SetInternalField( 1, External::New( matrix ) );
            matrix->is_slice = false;

            // Build array of CArrays
            args.This()->SetInternalField( 0, Int32::New(TYPE_MATRIX_DOUBLE) );
            args.This()->Set( String::New("__data__"), External::New(matrix));
            for (int i=0; i<matrix->rows; i++)
            {
                HandleScope handle_scope;

                // Build row descriptor
                cmatrix_double_row *row = new cmatrix_double_row;
                row->size = matrix->cols;
                row->data = matrix->data + i * matrix->cols;
                Handle<Value> RowPtr = External::New(row);
                Handle<Value> Test = IntegerRow->Get( Integer::New(0) );

                args.This()->Set( Int32::New(i), DoubleRow->NewInstance( 1, &RowPtr ) );
            }
        %>

        Destructor = <%
            cmatrix_double *matrix = (cmatrix_double *)Local<External>::Cast( obj->ToObject()->GetInternalField( 1 ) )->Value();
            if ( matrix->is_slice )
                delete matrix->data;
            delete matrix;
        %>

        Getters.length = <%
            cmatrix_double *matrix = (cmatrix_double *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            return Int32::New( matrix->rows );
        %>

        Getters.size = <%
            cmatrix_double *matrix = (cmatrix_double *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            Handle<Array> Result = Array::New();
            Result->Set( Int32::New( 0 ), Int32::New( matrix->rows ) );
            Result->Set( Int32::New( 1 ), Int32::New( matrix->cols ) );
            return Result;
        %>

        Methods.slice = <%
            cmatrix_double *matrix = (cmatrix_double *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            int start = ( args.Length() > 0 ) ? args[0]->Int32Value() : -1;
            int end = ( args.Length() > 0 ) ? args[1]->Int32Value() : -1;
            if ( end == -1 )
                end = matrix->rows;

            if ( args.Length() < 2 )
                return ThrowException( String::New( "Expected start and end" ) );
            if ( start < 0 || end > matrix->rows || end < 0 || start > matrix->rows || end < start )
                return ThrowException( String::New ( "Invalid start and end" ) );

            // Create new CMatrix
            Handle<Function> MatrixInteger = Handle<Function>::Cast( ClassDict->Get( String::New( "CMatrix_Double" ) ) );
            Handle<Value> Argv[] = { Int32::New( 1 ), Int32::New( 1 ) };
            Handle<Object> Obj = MatrixInteger->NewInstance( 2, Argv );
            cmatrix_double *new_matrix = (cmatrix_double *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();

            // Hijack data
            delete new_matrix->data;
            new_matrix->data = matrix->data + start*matrix->cols;
            new_matrix->cols = matrix->cols;
            new_matrix->rows = end - start;
            matrix->is_slice = true;

            // Copy objs
            for ( int i=start; i<end; i++ )
            {
                Handle<Value> Val = args.This()->Get( Int32::New( i ) );
                Obj->Set( Int32::New( i-start ), Val );
            }

            return Obj;
        %>

        Methods.Copy = <%
            cmatrix_double *matrix_a = (cmatrix_double *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            if ( args.Length() < 3 ) 
                return ThrowException( String::New( "Expected CArray, and dimensions" ) );
            int row = args[1]->Int32Value();
            int col = args[2]->Int32Value();

            if ( row < 0 || row >= matrix_a->rows )
                return ThrowException( String::New( "Row out of bounds" ) );
            if ( col < 0 || col >= matrix_a->cols )
                return ThrowException( String::New( "Column out of bounds" ) );

            if ( args[0]->IsObject() )
            {
                Handle<Object> Obj = args[0]->ToObject();
                if ( Obj->InternalFieldCount() == 2 )
                {
                    if ( Obj->GetInternalField(0)->Int32Value() == TYPE_MATRIX_DOUBLE )
                    {
                        cmatrix_double *matrix_b = (cmatrix_double *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();
                        if ( col == 0 && matrix_b->cols == matrix_a->cols  )
                        {
                            // Do one memcpy for speed
                            int rowLen = matrix_b->rows;
                            if ( row + rowLen > matrix_a->rows )
                                rowLen = matrix_a->rows - row;

                            double *ptrA = matrix_a->data + row*matrix_a->cols;
                            memcpy( ptrA, matrix_b->data, rowLen * matrix_a->cols * sizeof(double) );

                            return Undefined();
                        }
                        else
                        {
                            // Copy each row one-by-one
                            int rowLen = matrix_b->rows;
                            int colLen = matrix_b->cols;
                            if ( row + rowLen > matrix_a->rows )
                                rowLen = matrix_a->rows - row;
                            if ( col + colLen > matrix_a->cols )
                                colLen = matrix_a->cols - col;

                            double *ptrA = matrix_a->data + row*matrix_a->cols;
                            ptrA = (double *)( (char *)ptrA + col*sizeof(double) );

                            double *ptrB = matrix_b->data;
                            for ( int i=0; i<rowLen; i++ )
                            {
                                memcpy( ptrA, ptrB, colLen * sizeof(double) );
                                ptrA += matrix_a->cols;
                                ptrB += matrix_b->cols;
                            }

                            return Undefined();
                        }
                    }
                }

            }

            return ThrowException( String::New( "Could not copy array" ) );
        %>
    }

    // INTEGER MATRIX
    Classes.Integer = new Class ("Integer", 2);
    with (Classes.Integer) {
        Constructor = <%
            // Check arguments
            if ( args.Length() < 2 )
                return ThrowException( String::New( "Expected row, column count" ) );
            else if ( !args[0]->IsInt32() )
                return ThrowException( String::New( "Row count must be integer" ) );
            else if ( !args[1]->IsInt32() )
                return ThrowException( String::New( "Column count must be integer" ) );
            else if ( args[0]->IntegerValue() < 1 )
                return ThrowException( String::New( "Invalid row count" ) );
            else if ( args[1]->IntegerValue() < 1 )
                return ThrowException( String::New( "Invalid column count" ) );

            // Allocate space
            cmatrix_int *matrix = new cmatrix_int;
            matrix->rows = args[0]->Int32Value();
            matrix->cols = args[1]->Int32Value();
            int size = matrix->rows * matrix->cols;
            matrix->data = new int[size];
            args.This()->SetInternalField( 1, External::New( matrix ) );
            matrix->is_slice = false;

            // Build array of CArrays
            args.This()->SetInternalField( 0, Int32::New(TYPE_MATRIX_INTEGER) );
            for (int i=0; i<matrix->rows; i++)
            {
                HandleScope handle_scope;

                // Build row descriptor
                cmatrix_int_row *row = new cmatrix_int_row;
                row->size = matrix->cols;
                row->data = matrix->data + i * matrix->cols;
                Handle<Value> RowPtr = External::New(row);
                Handle<Value> Test = IntegerRow->Get( Integer::New(0) );

                args.This()->Set( Int32::New(i), IntegerRow->NewInstance( 1, &RowPtr ) );
            }
        %>

        Destructor = <%
            cmatrix_int *matrix = (cmatrix_int *)Local<External>::Cast( obj->ToObject()->GetInternalField( 1 ) )->Value();
            if ( matrix->is_slice )
                delete matrix->data;
            delete matrix;
        %>

        Getters.length = <%
            cmatrix_int *matrix = (cmatrix_int *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            return Int32::New( matrix->rows );
        %>

        Getters.size = <%
            cmatrix_int *matrix = (cmatrix_int *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            Handle<Array> Result = Array::New();
            Result->Set( Int32::New( 0 ), Int32::New( matrix->rows ) );
            Result->Set( Int32::New( 1 ), Int32::New( matrix->cols ) );
            return Result;
        %>

        Methods.slice = <%
            cmatrix_int *matrix = (cmatrix_int *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            int start = ( args.Length() > 0 ) ? args[0]->Int32Value() : -1;
            int end = ( args.Length() > 0 ) ? args[1]->Int32Value() : -1;
            if ( end == -1 )
                end = matrix->rows;

            if ( args.Length() < 2 )
                return ThrowException( String::New( "Expected start and end" ) );
            if ( start < 0 || end > matrix->rows || end < 0 || start > matrix->rows || end < start )
                return ThrowException( String::New ( "Invalid start and end" ) );

            // Create new CMatrix
            Handle<Function> MatrixInteger = Handle<Function>::Cast( ClassDict->Get( String::New( "CMatrix_Integer" ) ) );
            Handle<Value> Argv[] = { Int32::New( 1 ), Int32::New( 1 ) };
            Handle<Object> Obj = MatrixInteger->NewInstance( 2, Argv );
            cmatrix_int *new_matrix = (cmatrix_int *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();

            // Hijack data
            delete new_matrix->data;
            new_matrix->data = matrix->data + start*matrix->cols;
            new_matrix->cols = matrix->cols;
            new_matrix->rows = end - start;
            matrix->is_slice = true;

            // Copy objs
            for ( int i=start; i<end; i++ )
            {
                Handle<Value> Val = args.This()->Get( Int32::New( i ) );
                Obj->Set( Int32::New( i-start ), Val );
            }

            return Obj;
        %>

        Methods.Copy = <%
            cmatrix_int *matrix_a = (cmatrix_int *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            if ( args.Length() < 3 ) 
                return ThrowException( String::New( "Expected CArray, and dimensions" ) );
            int row = args[1]->Int32Value();
            int col = args[2]->Int32Value();

            if ( row < 0 || row >= matrix_a->rows )
                return ThrowException( String::New( "Row out of bounds" ) );
            if ( col < 0 || col >= matrix_a->cols )
                return ThrowException( String::New( "Column out of bounds" ) );

            bool good = false;
            if ( args[0]->IsObject() )
            {
                Handle<Object> Obj = args[0]->ToObject();
                if ( Obj->InternalFieldCount() == 2 )
                {
                    if ( Obj->GetInternalField(0)->Int32Value() == TYPE_MATRIX_INTEGER )
                    {
                        cmatrix_int *matrix_b = (cmatrix_int *)Local<External>::Cast( Obj->GetInternalField( 1 ) )->Value();
                        if ( col == 0 && matrix_b->cols == matrix_a->cols  )
                        {
                            // Do one memcpy for speed
                            int rowLen = matrix_b->rows;
                            if ( row + rowLen > matrix_a->rows )
                                rowLen = matrix_a->rows - row;

                            int *ptrA = matrix_a->data + row*matrix_a->cols;
                            memcpy( ptrA, matrix_b->data, rowLen * matrix_a->cols * sizeof(int) );

                            return Undefined();
                        }
                        else
                        {
                            // Copy each row one-by-one
                            int rowLen = matrix_b->rows;
                            int colLen = matrix_b->cols;
                            if ( row + rowLen > matrix_a->rows )
                                rowLen = matrix_a->rows - row;
                            if ( col + colLen > matrix_a->cols )
                                colLen = matrix_a->cols - col;

                            int *ptrA = matrix_a->data + row*matrix_a->cols + col;
                            int *ptrB = matrix_b->data;
                            for ( int i=0; i<rowLen; i++ )
                            {
                                memcpy( ptrA, ptrB, colLen * sizeof(int) );
                                ptrA += matrix_a->cols;
                                ptrB += matrix_b->cols;
                            }
                             
                            return Undefined();   
                        }
                    }
                }

            }

            return ThrowException( String::New( "Could not copy array" ) );
        %>
    }

    Export();
}
